Processing and analysis of the impedance data generated for xCellAnalyze: A Framework for the Analysis of Cellular Impedance Measurements for Mode of Action Discovery

Author: Raimo Franke

N.B.: Datasets 1 to 10 were generated on xCelligence machine A and datasets 11 to 16 were generated on xCelligence machine B

Load libraries

library("tidyverse")
library("stringr")
library("knitr")
library("gtools")
library("gplots")
library("dendsort")
library("pheatmap")
library("RColorBrewer")

Source the xCELLanalyzer functions:

  • read_xcell: This function reads the tab-delimeted data exported from the RTCA Software Version 1.2. If the naming conventions are followed, only the global my_filepath varible and the experiment ID are used as arguments to the function. The cryptic column names generated by the export from the RTCA Software are fixed to only contain the well identifier (of the E-plate). To match the well labels with the compound IDs an _anno.txt is read containing the annotation of the wells for the appropriate experiment. The column names are then replaced with the corresponding compound IDs.

  • edit_df: This function takes the dataframe generated by the read_xcell-function and the experiment ID as arguments. In the first row of the raw data file the time point of the last measurement before compound addition was pasted in manually. The function takes this value and creates a tibble that only includes the last measurement before and 800 measurements after compound addition.

  • normalize_xcell: This function performs a global normalization by dividing each cell index value recorded after compound addition by the last cell index recorded before compound addition,

  • do_median_polish: This function applies the median polish algorithm on the technical replicates and returns a dataframe with three colums giving the range of the residuals, the column effect and the sum of the residuals. Additionally, a pdf- file is generated that plots the normalized cell index over time for each set of replicates.

  • calculate_median_curves: This function calculates median normalized cell index values for each set of technical replicates. A dataframe with the normalized, median TCRPs is returned.

  • normalize_dmso: This function performs a local normalization of each median compound TCRP by subtracting the normalized cell index of the DMSO control at each time point. This is done for each independent experiment to make them more comparable and to address potential batch effects.

  • remove_dmso: This function removes the DMSO control from the dataset after local normalization.

  • score1.function: This function takes the dataframe with basis spline coefficients for each compound as an argument and calculates a distance matrix. The distance measure can be provided as argument to the function. The distance matrix is sorted and for each replicate a rank sum is calculated. A normalized score is calculated by division of the ideal score of a set of replicates by the obtained score. The closer this value is to 1 the better the reproducibility. The function returns a list with the compound names, number of replicates per group, score and normalized score.

source("xCell_functions.R")

Process the 12 xCelligence runs with the xcell_process_data.R script.

The xcell_process_data.R scripte processes the 12 datasets in the following manner: The raw data are read and the dataframe is edited to contain only the last measurement before compound addition and 800 measurements after compound addition. Then the global normalization is performed and the do_median_polish function is applied to identify outliers. Outliers with absolute residual sum of greater than 90 are removed. Median TCRPs are calculated and normalized locally by subtracting the DMSO control TCRP. A dataframe without normalization with the DMSO control is also generated for comparisons.

Plot figure 2

par(mfrow = c(1,2))
plot(x=rownames(xcell_median_7), y=xcell_median_7[,"7_CytochalasinD"], type="l", col="blue", lwd = 2,
     main = "Actin", cex.main=1, ylim=c(0,3), ylab="NCI", xlab="t [h]")
lines(x=rownames(xcell_median_7), y=xcell_median_7[,"7_ChondramidC"], type="l", col="green", lwd = 2)
legend("topleft",legend=c("Cytochalasin D", "Chondramide C"),
         col= c("blue", "green"),pch=c(16,18),bty="n",ncol=1,cex=0.8,pt.cex=1)
plot(x=rownames(xcell_median_7), y=xcell_median_7[,"7_MG132"], type="l", col="red", lwd = 2, 
     main = "Proteasome", cex.main=1, ylim=c(0,3), ylab="NCI", xlab="t [h]")
lines(x=rownames(xcell_median_7), y=xcell_median_7[,"7_Bortezomib"], type="l", col="purple", lwd = 2)
legend("topleft",legend=c("MG132", "Bortezomib"),
       col= c("blue", "green"),pch=c(16,18),bty="n",ncol=1,cex=0.8,pt.cex=1)

NA

Combine all matrices containing the normalized median TCRP data from each run in one big matrix and reorder the column names alphabetically

median.combined <- cbind(xcell_median_norm_1, xcell_median_norm_2, xcell_median_norm_3,
                         xcell_median_norm_4, xcell_median_norm_5, xcell_median_norm_6,
                         xcell_median_norm_7, xcell_median_norm_8, xcell_median_norm_9,
                         xcell_median_norm_10, xcell_median_norm_11, xcell_median_norm_12,
                         xcell_median_norm_13, xcell_median_norm_14, xcell_median_norm_15,
                         xcell_median_norm_16)
for (i in 1 : length(median.combined[1,])){
  temp <- unlist(strsplit(toString(colnames(median.combined)[i]), "_"))
  colnames(median.combined)[i] <- paste(temp[2], temp[1], sep="_")
}
median.combined.ordered <- median.combined[,mixedorder(colnames(median.combined))]

Generate an analogous matrix without the DMSO normalization

median.combined_notnorm <- cbind(
                         xcell_median_notnorm_1, xcell_median_notnorm_2, xcell_median_notnorm_3,
                         xcell_median_notnorm_4, xcell_median_notnorm_5, xcell_median_notnorm_6,
                         xcell_median_notnorm_7, xcell_median_notnorm_8, xcell_median_notnorm_9,
                         xcell_median_notnorm_10, xcell_median_notnorm_11, xcell_median_notnorm_12,
                         xcell_median_notnorm_13, xcell_median_notnorm_14, xcell_median_notnorm_15,
                         xcell_median_notnorm_16)
for (i in 1 : length(median.combined_notnorm[1,])){
  temp <- unlist(strsplit(toString(colnames(median.combined_notnorm)[i]), "_"))
  colnames(median.combined_notnorm)[i] <- paste(temp[2], temp[1], sep="_")
}
median.combined_notnorm.ordered <- 
  median.combined_notnorm[,mixedorder(colnames(median.combined_notnorm))]

Calculate smoothing splines

newrownames <- read.delim("newrownames.txt", header=F, stringsAsFactors = FALSE)
#smoothing splines with new rownames
median.sp<-matrix(ncol=22, nrow=219)
row.names(median.sp)<-newrownames$V1
t<-rownames(median.combined.ordered)
t<-as.numeric(t)
i<-0
repeat{
  i<-i+1
  temp<-smooth.spline(x=t, y= median.combined.ordered[,i], nknots=20)
  median.sp[i,]<-temp$fit$coef
  if (i==219) break
}
#analogous for the data without local normalization
median.sp.notnorm<-matrix(ncol=22, nrow=219)
row.names(median.sp.notnorm)<-newrownames$V1
t<-rownames(median.combined_notnorm.ordered)
t<-as.numeric(t)
i<-0
repeat{
  i<-i+1
  temp<-smooth.spline(x=t, y= median.combined_notnorm.ordered[,i], nknots=20)
  median.sp.notnorm[i,]<-temp$fit$coef
  if (i==219) break
}

Calculate score for biological replicates

The goal is to evalutate reproducibility for each compound and to judge which compounds are well reproducible and which not so much. For this purpose a rank-based score is calculated for each compound, the closer to one the better. In addition an overall score is calculated, which is a single number to judge how the experiments and the data analysis overall performed. Again it is a rank-based score, the closer to one the better.

Here we also want to optimize certain parameters for the data analysis, namely the distance meassure and data scaling / centering.

Distance measures (source: https://stat.ethz.ch/R-manual/R-devel/library/stats/html/dist.html) compared are (written for two vectors x and y):

euclidean: Usual distance between the two vectors (2 norm aka L_2), sqrt(sum((x_i - y_i)^2)).

maximum: Maximum distance between two components of x and y (supremum norm)

manhattan: Absolute distance between the two vectors (1 norm aka L_1).

Scaling and Centering of the matrices using the scale function of R base: R documentation: “The value of center determines how column centering is performed. If center is a numeric vector with length equal to the number of columns of x, then each column of x has the corresponding value from center subtracted from it. If center is TRUE then centering is done by subtracting the column means of x from their corresponding columns, and if center is FALSE, no centering is done.

The value of scale determines how column scaling is performed (after centering). If scale is a numeric vector with length equal to the number of columns of x, then each column of x is divided by the corresponding value from scale. If scale is TRUE then scaling is done by dividing the (centered) columns of x by their root-mean-square, and if scale is FALSE, no scaling is done.

The root-mean-square for a column is obtained by computing the square-root of the sum-of-squares of the non-missing values in the column divided by the number of non-missing values minus one."

#criterion how the overall procedure scored 
res <- score1.function(median.sp, "euclidean")
euclidean <- sum(res$normscore)/i
median.sp.scaled <- scale(median.sp, center = FALSE, scale = TRUE)
res <- score1.function(median.sp.scaled, "euclidean")
euclidean.scaled <- sum(res$normscore)/i 
median.sp.centered.scaled <- scale(median.sp, center = TRUE, scale = TRUE)
res <- score1.function(median.sp.centered.scaled, "euclidean")
euclidean.centered.scaled <- sum(res$normscore)/i 
###
res <- score1.function(median.sp, "maximum")
maximum <- sum(res$normscore)/i
res <- score1.function(median.sp.scaled, "maximum")
maximum.scaled <- sum(res$normscore)/i 
res <- score1.function(median.sp.centered.scaled, "maximum")
maximum.centered.scaled <- sum(res$normscore)/i
###
res <- score1.function(median.sp, "manhattan")
manhattan <- sum(res$normscore)/i
res <- score1.function(median.sp.scaled, "manhattan")
manhattan.scaled <- sum(res$normscore)/i
res <- score1.function(median.sp.centered.scaled, "manhattan")
manhattan.centered.scaled <- sum(res$normscore)/i
###
not_scaled <- c(euclidean, maximum, manhattan)
scaled <- c(euclidean.scaled, maximum.scaled, manhattan.scaled)
cent_scaled <- c(euclidean.centered.scaled, maximum.centered.scaled, manhattan.centered.scaled)
tab1 <- rbind(not_scaled, scaled, cent_scaled)
colnames(tab1) <- c("euclidean", "maximum", "manhattan")
tab1 <- as.data.frame(tab1)
#grid.table(round(tab1, 3))
kable(round(tab1, 3), caption = "scores for 5 distance measures +/- scaling and centering")
euclidean maximum manhattan
not_scaled 0.378 0.404 0.342
scaled 0.479 0.473 0.412
cent_scaled 0.479 0.473 0.410

*Result: Euclidean distance with scaled (not centred) data performed best.

Now we want to investigate if the local normalization with the TCRP from DMSO treated cells for each run lead to an improvement of reproducibility.

median.sp.notnorm.scaled <- scale(median.sp.notnorm, center = FALSE, scale = TRUE)
res <- score1.function(median.sp.notnorm.scaled, "euclidean")
euclidean.scaled.notnorm <- sum(res$normscore)/i 
median.sp.scaled <- scale(median.sp, center = FALSE, scale = TRUE)
res <- score1.function(median.sp.scaled, "euclidean")
euclidean.scaled <- sum(res$normscore)/i 
cat(paste0("without local normalization: ", round(euclidean.scaled.notnorm, 3),
           "\nwith local normalization: ", round(euclidean.scaled,3)))
without local normalization: 0.274
with local normalization: 0.479

Clearly the local normalization has lead to a strong improvement: 0.479 >> 0.274.

Evaluate reproducibility of biological replicates group-wise, calculate a score for each group of replicates

res <- score1.function(median.sp, "euclidean")
groupmatch <- read.delim("groupmatch.txt", header=F)$V1
group.score <- c()
for(i in 1:length(groupmatch)){
  
  ma <- grep(groupmatch[i], res$rep)
  gscore <- sum(res$normscore[ma])/length(ma)
  group.score <- c(group.score, gscore)
  
}
group.result <- data.frame(groupmatch,group.score)
group.result <- group.result[order(-group.result$group.score),]
kable(group.result)

groupmatch group.score
15 Chelerythrine 1.0000000
28 H89 1.0000000
48 SaframycinMx1 1.0000000
54 Staurosporine 1.0000000
60 Wortmannin 1.0000000
19 Cycloheximide 0.9250000
14 Cerulenin 0.9166667
7 Apicidin 0.8333333
57 TubulysinB 0.7857143
2 ActinomycinD 0.7555556
5 Anisomycin 0.6807359
32 Mevastatin 0.6750000
45 Rapamycin 0.6427947
47 Rhizopodin 0.5454259
21 Cytochalasin 0.5416667
23 Emetine 0.5000000
41 PD169316 0.4908789
20 CyclosporinA 0.4810458
16 ChondramidC 0.4530303
33 MG132 0.4431241
44 PurvalanolA 0.4395161
4 Amanitin 0.4377358
42 Podophyllotoxin 0.4328454
11 Bortezomib 0.4243003
58 Vinblastin 0.4202786
43 Puromycin 0.4164809
29 Indirubin3monoxime 0.3968689
17 Colchicine 0.3932894
3 Alsterpaullone 0.3887535
1 A23187 0.3631470
8 Apicularen 0.3248723
34 Myriaporone 0.2915516
35 MyxothiazolA 0.2776854
50 SB203580 0.2615083
51 Scriptaid 0.2605042
56 Trichostatin 0.2388983
37 Nocodazol 0.2386498
59 Vioprolide 0.2304310
25 Etoposide 0.1995340
27 Griseofulvin 0.1931039
13 CCCP 0.1835840
49 SB202190 0.1775103
53 Soraphen 0.1771044
10 ArgyrinA 0.1745614
6 Aphidicolin 0.1530034
52 Simvastatin 0.1428155
24 EpothiloneB 0.1351025
26 GephyronicAcidA 0.1312164
38 OkadaicAcid 0.1064823
31 Methotrexate 0.1038177
12 Camptothecin 0.1002997
46 RatjadonC 0.0859606
55 Taxol 0.0708859
30 LY294002 0.0632620
22 Doxorubicin 0.0558336
18 CruentarenA 0.0545505
39 Oligomycin 0.0538105
40 Oxamflatin 0.0514741
36 Neopeltolide 0.0459717
9 ArchazolidB 0.0348333

write.csv2(group.result, file = "group_results.csv")

What we can do now is to use the score calculated for each biological replicate and define a threshold to remove replicates which are outliers. And then calculate the groupwise scores and the overall score again to check the improvement.

#Filter reference set
#normailzed score < 0.1 is defined as outlier
res <- score1.function(median.sp, "euclidean")
my_hitlist <- res$rep[res$normscore < 0.1]
my_outliers <- c()
for (i in 1: length(my_hitlist)) {
temp <- unlist(strsplit(toString(my_hitlist[i]), "_"))
new_name <- paste0(temp[2], "_", temp[3])
my_outliers <- c(my_outliers, new_name)
}
print(my_outliers)
 [1] "Apicularen_1"         "ArchazolidB_3"        "ArchazolidB_6"        "ArchazolidB_13"      
 [5] "ArgyrinA_4"           "Bortezomib_9"         "Camptothecin_4"       "Camptothecin_12"     
 [9] "Colchicine_16"        "CruentarenA_4"        "CruentarenA_7"        "CruentarenA_14"      
[13] "Doxorubicin_2"        "Doxorubicin_9"        "Doxorubicin_10"       "Doxorubicin_11"      
[17] "Doxorubicin_12"       "EpothiloneB_13"       "Etoposide_3"          "Etoposide_15"        
[21] "GephyronicAcidA_9"    "GephyronicAcidA_10"   "GephyronicAcidA_13"   "Indirubin3monoxime_3"
[25] "LY294002_2"           "LY294002_5"           "LY294002_11"          "LY294002_13"         
[29] "Methotrexate_1"       "Myriaporone_16"       "Neopeltolide_2"       "Neopeltolide_6"      
[33] "Neopeltolide_16"      "Nocodazole_15"        "OkadaicAcid_2"        "Oligomycin_1"        
[37] "Oligomycin_7"         "Oligomycin_15"        "Oligomycin_16"        "Oxamflatin_4"        
[41] "Oxamflatin_8"         "Oxamflatin_16"        "PD169316_9"           "RatjadonC_2"         
[45] "RatjadonC_9"          "RatjadonC_11"         "RatjadonC_12"         "Simvastatin_9"       
[49] "Taxol_1"              "Taxol_6"              "Taxol_7"              "Taxol_13"            
[53] "Taxol_14"             "Vioprolide_10"       

Plot the biological replicates of each compound (generated by the medians of the technical replicates on each E-plate) with and without local normalization.

#pdf(file = "median_TCRP.pdf", paper = "a4")
par(mfrow = c(1,2))
i<-0
repeat{
  i<-i+1
  
  
  ma<-grep(groupmatch[i], colnames(median.combined_notnorm.ordered)) 
  z<-median.combined_notnorm.ordered[,ma]
  z<-as.data.frame(z)
  z<-z[,mixedorder(names(z))]
  
  
  plot(x=rownames(z), y=z[,1], type="l", col="blue", main = "not normalized",
       cex.main=0.8, ylim=c(0,3), ylab="NCI", xlab="t [h]")
  
  
  
  myC <- length(ma)
  myColor <- c("green", "red", "black", "orange")
  
  for (n in 1:(myC-1))
  {
    lines(x=rownames(z), y=z[,n+1], type="l", col=myColor[n])
  }
  legend("topleft",legend=colnames(z),
         col= c("blue", myColor),pch=c(16,18),bty="n",ncol=1,cex=0.6,pt.cex=0.7)
  
  z<-median.combined.ordered[,ma]
  z<-as.data.frame(z)
  z<-z[,mixedorder(names(z))]
  
  
  plot(x=rownames(z), y=z[,1], type="l", col="blue", main = "normalized", 
       cex.main=0.8, ylim=c(-1.5,1.5), ylab="NCI", xlab="t [h]")
  
  
  for (n in 1:(myC-1))
  {
    lines(x=rownames(z), y=z[,n+1], type="l", col=myColor[n])
  }
  legend("topleft",legend=colnames(z),
         col= c("blue", myColor),pch=c(16,18),bty="n",ncol=1,cex=0.6,pt.cex=0.7)
  
  
  
  
  if (i==60) break
}

#dev.off()

In the case where more than one biological replicate or one compound was found to be below the threshold, the one that deviates the most is removed from the data.

my_outliers_selected <- c("Apicularen_1", "ArchazolidB_13", "ArgyrinA_4", "Camptothecin_4", "Colchicine_16", "CruentarenA_14", "Doxorubicin_9", "EpothiloneB_13", "Etoposide_3", "GephyronicAcidA_9", "Indirubin3monoxime_3", "LY294002_2", "Methotrexate_1", "MG132_16", "Myriaporone_3", "Neopeltolide_16", "Nocodazole_15", "OkadaicAcid_2", "Oligomycin_15", "Oxamflatin_16", "PD169316_9", "RatjadonC_9", "Simvastatin_9", "Taxol_6", "Trichostatin_4", "Bortezomib_9", "Vioprolide_10")
ma <- match(my_outliers_selected, colnames(median.combined.ordered))
median.combined.edited <- median.combined.ordered[, -ma]
#calculate cubic smoothing splines
median.sp.edited<-matrix(ncol=22, nrow=192)
row.names(median.sp.edited)<-newrownames$V1[-ma]
t<-rownames(median.combined.edited)
t<-as.numeric(t)
i<-0
repeat{
  i<-i+1
  temp<-smooth.spline(x=t, y= median.combined.edited[,i], nknots=20)
  median.sp.edited[i,]<-temp$fit$coef
  if (i==192) break
}
median.sp.edited.scaled <- scale(median.sp.edited, center = FALSE, scale = TRUE)
res <- score1.function(median.sp.edited.scaled, "euclidean")
euclidean.scaled <- sum(res$normscore)/i 
cat(paste0("Euclidean.scaled after removal of outliers: ", round(euclidean.scaled, 3)))
Euclidean.scaled after removal of outliers: 0.647
group.score <- c()
for(i in 1:length(groupmatch)){
  
  ma <- grep(groupmatch[i], res$rep)
  gscore <- sum(res$normscore[ma])/length(ma)
  group.score <- c(group.score, gscore)
  
}
group.result <- data.frame(groupmatch,group.score)
group.result <- group.result[order(-group.result$group.score),]
kable(group.result)

groupmatch group.score
8 Apicularen 1.0000000
10 ArgyrinA 1.0000000
14 Cerulenin 1.0000000
15 Chelerythrine 1.0000000
17 Colchicine 1.0000000
28 H89 1.0000000
30 LY294002 1.0000000
31 Methotrexate 1.0000000
36 Neopeltolide 1.0000000
40 Oxamflatin 1.0000000
48 SaframycinMx1 1.0000000
54 Staurosporine 1.0000000
56 Trichostatin 1.0000000
57 TubulysinB 1.0000000
60 Wortmannin 1.0000000
1 A23187 0.9523810
24 EpothiloneB 0.9523810
19 Cycloheximide 0.9304348
29 Indirubin3monoxime 0.8484848
41 PD169316 0.8484848
2 ActinomycinD 0.8333333
59 Vioprolide 0.8333333
5 Anisomycin 0.8320261
7 Apicidin 0.8055556
20 CyclosporinA 0.7777778
21 Cytochalasin 0.7619048
4 Amanitin 0.7023810
45 Rapamycin 0.6944444
3 Alsterpaullone 0.6835017
42 Podophyllotoxin 0.6631485
51 Scriptaid 0.6395604
50 SB203580 0.6392857
18 CruentarenA 0.6250000
43 Puromycin 0.6110276
16 ChondramidC 0.5865801
52 Simvastatin 0.5578866
11 Bortezomib 0.5544890
32 Mevastatin 0.5500000
37 Nocodazol 0.5454981
58 Vinblastin 0.5440476
44 PurvalanolA 0.5438596
38 OkadaicAcid 0.5435235
23 Emetine 0.5158730
12 Camptothecin 0.5142857
47 Rhizopodin 0.4989508
25 Etoposide 0.4779202
33 MG132 0.4687747
49 SB202190 0.3631785
6 Aphidicolin 0.3492424
27 Griseofulvin 0.3425232
53 Soraphen 0.3399933
34 Myriaporone 0.3336412
55 Taxol 0.3334500
35 MyxothiazolA 0.2868851
13 CCCP 0.2359447
22 Doxorubicin 0.1784604
9 ArchazolidB 0.1758621
26 GephyronicAcidA 0.1675484
46 RatjadonC 0.1181932
39 Oligomycin 0.0831636

write.csv2(group.result, "group_result_filter.csv")

With the improved data the medians of the biological replicates is calculated.

#Calculate medians of medians
median.combined.median<-matrix(ncol=60, nrow=800)
colnames(median.combined.median)<-groupmatch
rownames(median.combined.median)<-row.names(median.combined.edited)
i<-0
repeat{
  i<-i+1
  
  name<-groupmatch[i]
  ma<-grep(groupmatch[i], colnames(median.combined.edited))
  z<-median.combined.edited[,ma]
  median.combined.median[,i]<-apply(z, 1, median) 
  
  if (i==60) break
}
#smoothing splines for median.combined
xmedian.combined<-matrix(ncol=22, nrow=60)
row.names(xmedian.combined)<-colnames(median.combined.median)
t<-rownames(median.combined.median)
t<-as.numeric(t)
i<-0
repeat{
  i<-i+1
  temp<-smooth.spline(x=t, y= median.combined.median[,i], nknots=20)
  xmedian.combined[i,]<-temp$fit$coef
  if (i==60) break
}
#Scaling
xmedian.combined.scaled <- scale(xmedian.combined, scale = TRUE, center = FALSE)
colnames(xmedian.combined.scaled) <- c("c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
                                       "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17",
                                       "c18", "c19", "c20", "c21", "c22")
##distmat and hierarchical clustering
xmedian.combined.distmat <- dist(xmedian.combined.scaled, method = "euclidean")
xmedian.hclust.sorted <- dendsort(hclust(xmedian.combined.distmat, method = "complete"))
par(cex = 0.6)
plot(as.dendrogram(xmedian.hclust.sorted))

#heatmap
my_color = colorRampPalette(rev(brewer.pal(n = 10, name = "RdYlBu")))(100)
pheatmap(xmedian.combined.scaled, cluster_rows = xmedian.hclust.sorted, cluster_cols = TRUE,
         color = my_color, fontsize = 5.0)

Rank-based MoA prediction

xmedian.combined.scaled <- scale(xmedian.combined)
mydistmat <- dist(xmedian.combined.scaled, method = "euclidean")
mydistmat <- as.matrix(mydistmat)
rank.predict <- matrix(ncol=60, nrow=60)
colnames(rank.predict) <- colnames(mydistmat)
rownames(rank.predict) <- c(1:60)
for (i in 1:60){
mydistmat.ordered <- mydistmat[order(mydistmat[,i]),]
rank.predict[,i] <- rownames(mydistmat.ordered) 
}
rank <- 1:59
rank.predict <- rank.predict[-1,]
rank.predict <- as.data.frame(cbind(rank, rank.predict))
write.csv2(rank.predict, "rankpredict.csv")
rank.predict

DMSO pilot test for figure 1

#working directory
setwd("./DMSO_test/")
#compounds
DMSO_test.raw<-read.csv2(file="DMSO_test_raw.csv", header=T)
###############################################
#Normalization
x<-as.matrix(DMSO_test.raw[,2:97])
norm<-x[1,]
norm<-as.vector(norm)
DMSO_test.norm<-x/rep(norm, each = nrow(x))
DMSO_test.norm<-DMSO_test.norm[2:163,] #remove first row (last measurement before compound addition)
    
          
#set measurement
DMSO_test.norm<-DMSO_test.norm[1:150,]
DMSO_test.norm <- as.data.frame(DMSO_test.norm)
#boxplot
postscript("figure_1.eps", width = 860, height = 600)
par(mar=c(5,3,2,2)+0.1)
boxplot(DMSO_test.norm,  ylab = "NCI", xlab = "well position", cex.axis=0.4, las=2, col = "lightgray")
dev.off()
null device 
          1 
# plot the TCRPs
my_timepoints <- DMSO_test.raw[2:151,]$t - DMSO_test.raw[2,]$t
rownames(DMSO_test.norm) <- my_timepoints
#pdf(file = "DMSO_controls.pdf", paper = "a4r")
par(mfrow = c(2,3))

for (i in 1: ncol(DMSO_test.norm)){
plot(DMSO_test.norm[,i], type="l", col="blue",
     main=colnames(DMSO_test.norm)[i],cex.main=0.8, ylim=c(0.8,2), ylab="NCI", xlab="t [h]")
}

#dev.off()
#calculate medians
my_matrix <- as.matrix(DMSO_test.norm)
col_medians <- apply(my_matrix, 2, median)
#wilcox test
col_medians["G7"]<- NA
col_medians["H2"] <- NA
col_medians["H5"] <- NA
col_medians["H10"] <- NA
row_A <- col_medians[1:12]
row_B <- col_medians[13:24]
row_C <- col_medians[25:36]
row_D <- col_medians[37:48]
row_E <- col_medians[49:60]
row_F <- col_medians[61:72]
row_G <- col_medians[73:84]
row_G["G6"] <- NA
row_G["G7"] <- NA
row_H <- col_medians[85:96]
row_H["H5"] <- NA
row_H["H10"] <- NA
outer_rows <- c(row_A, row_H)
inner_rows <- c(row_B, row_C, row_D, row_E, row_F, row_G)
median(outer_rows, na.rm = T)
[1] 1.357712
median(inner_rows, na.rm = T)
[1] 1.310951
wilcox.test(outer_rows, inner_rows, na.rm = T, alternative = "two.sided")

    Wilcoxon rank sum test with continuity correction

data:  outer_rows and inner_rows
W = 1122, p-value = 0.0002719
alternative hypothesis: true location shift is not equal to 0
LS0tCnRpdGxlOiAieENFTExhbmFseXplciAtIERhdGEgQW5hbHlzaXMiCm91dHB1dDoKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICB3b3JkX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgojIyMgUHJvY2Vzc2luZyBhbmQgYW5hbHlzaXMgb2YgdGhlIGltcGVkYW5jZSBkYXRhIGdlbmVyYXRlZCBmb3IgKnhDZWxsQW5hbHl6ZTogQSBGcmFtZXdvcmsgZm9yIHRoZSBBbmFseXNpcyBvZiBDZWxsdWxhciBJbXBlZGFuY2UgTWVhc3VyZW1lbnRzIGZvciBNb2RlIG9mIEFjdGlvbiBEaXNjb3ZlcnkqCgpfX0F1dGhvcjogUmFpbW8gRnJhbmtlX18KCk4uQi46IERhdGFzZXRzIDEgdG8gMTAgd2VyZSBnZW5lcmF0ZWQgb24geENlbGxpZ2VuY2UgbWFjaGluZSBBIGFuZCBkYXRhc2V0cyAxMSB0byAxNiB3ZXJlIGdlbmVyYXRlZCBvbiB4Q2VsbGlnZW5jZSBtYWNoaW5lIEIKCkxvYWQgbGlicmFyaWVzCgpgYGB7ciBsb2FkIGxpYnJhcmllcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSgidGlkeXZlcnNlIikKbGlicmFyeSgic3RyaW5nciIpCmxpYnJhcnkoImtuaXRyIikKbGlicmFyeSgiZ3Rvb2xzIikKbGlicmFyeSgiZ3Bsb3RzIikKbGlicmFyeSgiZGVuZHNvcnQiKQpsaWJyYXJ5KCJwaGVhdG1hcCIpCmxpYnJhcnkoIlJDb2xvckJyZXdlciIpCgpgYGAKCiMjIyMgU291cmNlIHRoZSB4Q0VMTGFuYWx5emVyIGZ1bmN0aW9uczoKCiogX19yZWFkX3hjZWxsOl9fClRoaXMgZnVuY3Rpb24gcmVhZHMgdGhlIHRhYi1kZWxpbWV0ZWQgZGF0YSBleHBvcnRlZCBmcm9tIHRoZSBSVENBIFNvZnR3YXJlIFZlcnNpb24gMS4yLiBJZiB0aGUgbmFtaW5nIGNvbnZlbnRpb25zIGFyZSBmb2xsb3dlZCwgb25seSB0aGUgZ2xvYmFsIG15X2ZpbGVwYXRoIHZhcmlibGUgYW5kIHRoZSBleHBlcmltZW50IElEIGFyZSB1c2VkIGFzIGFyZ3VtZW50cyB0byB0aGUgZnVuY3Rpb24uClRoZSBjcnlwdGljIGNvbHVtbiBuYW1lcyBnZW5lcmF0ZWQgYnkgdGhlIGV4cG9ydCBmcm9tIHRoZSBSVENBIFNvZnR3YXJlIGFyZSBmaXhlZCB0byBvbmx5IGNvbnRhaW4gdGhlIHdlbGwgaWRlbnRpZmllciAob2YgdGhlIEUtcGxhdGUpLgpUbyBtYXRjaCB0aGUgd2VsbCBsYWJlbHMgd2l0aCB0aGUgY29tcG91bmQgSURzIGFuIF9hbm5vLnR4dCBpcyByZWFkIGNvbnRhaW5pbmcgdGhlIGFubm90YXRpb24gb2YgdGhlIHdlbGxzIGZvciB0aGUgYXBwcm9wcmlhdGUgZXhwZXJpbWVudC4gVGhlIGNvbHVtbiBuYW1lcyBhcmUgdGhlbiByZXBsYWNlZCB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIGNvbXBvdW5kIElEcy4KCiogX19lZGl0X2RmOl9fClRoaXMgZnVuY3Rpb24gdGFrZXMgdGhlIGRhdGFmcmFtZSBnZW5lcmF0ZWQgYnkgdGhlIHJlYWRfeGNlbGwtZnVuY3Rpb24gYW5kIHRoZSBleHBlcmltZW50IElEIGFzIGFyZ3VtZW50cy4gSW4gdGhlIGZpcnN0IHJvdyBvZiB0aGUgcmF3IGRhdGEgZmlsZSB0aGUgdGltZSBwb2ludCBvZiB0aGUgbGFzdCBtZWFzdXJlbWVudCBiZWZvcmUgY29tcG91bmQgYWRkaXRpb24gd2FzIHBhc3RlZCBpbiBtYW51YWxseS4gVGhlIGZ1bmN0aW9uIHRha2VzIHRoaXMgdmFsdWUgYW5kIGNyZWF0ZXMgYSB0aWJibGUgdGhhdCBvbmx5IGluY2x1ZGVzIHRoZSBsYXN0IG1lYXN1cmVtZW50IGJlZm9yZSBhbmQgODAwIG1lYXN1cmVtZW50cyBhZnRlciBjb21wb3VuZCBhZGRpdGlvbi4gCgoqIF9fbm9ybWFsaXplX3hjZWxsOl9fClRoaXMgZnVuY3Rpb24gcGVyZm9ybXMgYSBnbG9iYWwgbm9ybWFsaXphdGlvbiBieSBkaXZpZGluZyBlYWNoIGNlbGwgaW5kZXggdmFsdWUgcmVjb3JkZWQgYWZ0ZXIgY29tcG91bmQgYWRkaXRpb24gYnkgdGhlIGxhc3QgY2VsbCBpbmRleCByZWNvcmRlZCBiZWZvcmUgY29tcG91bmQgYWRkaXRpb24sCgoqIF9fZG9fbWVkaWFuX3BvbGlzaDpfXwpUaGlzIGZ1bmN0aW9uIGFwcGxpZXMgdGhlIG1lZGlhbiBwb2xpc2ggYWxnb3JpdGhtIG9uIHRoZSB0ZWNobmljYWwgcmVwbGljYXRlcyBhbmQgcmV0dXJucyBhIGRhdGFmcmFtZSB3aXRoIHRocmVlIGNvbHVtcyBnaXZpbmcgdGhlIHJhbmdlIG9mIHRoZSByZXNpZHVhbHMsIHRoZSBjb2x1bW4gZWZmZWN0IGFuZCB0aGUgc3VtIG9mIHRoZSByZXNpZHVhbHMuIEFkZGl0aW9uYWxseSwgYSBwZGYtIGZpbGUgaXMgZ2VuZXJhdGVkIHRoYXQgcGxvdHMgdGhlIG5vcm1hbGl6ZWQgY2VsbCBpbmRleCBvdmVyIHRpbWUgZm9yIGVhY2ggc2V0IG9mIHJlcGxpY2F0ZXMuCgoqIF9fY2FsY3VsYXRlX21lZGlhbl9jdXJ2ZXM6X18KVGhpcyBmdW5jdGlvbiBjYWxjdWxhdGVzIG1lZGlhbiBub3JtYWxpemVkIGNlbGwgaW5kZXggdmFsdWVzIGZvciBlYWNoIHNldCBvZiB0ZWNobmljYWwgcmVwbGljYXRlcy4gQSBkYXRhZnJhbWUgd2l0aCB0aGUgbm9ybWFsaXplZCwgbWVkaWFuIFRDUlBzIGlzIHJldHVybmVkLgoKKiBfX25vcm1hbGl6ZV9kbXNvOl9fClRoaXMgZnVuY3Rpb24gcGVyZm9ybXMgYSBsb2NhbCBub3JtYWxpemF0aW9uIG9mIGVhY2ggbWVkaWFuIGNvbXBvdW5kIFRDUlAgYnkgc3VidHJhY3RpbmcgdGhlIG5vcm1hbGl6ZWQgY2VsbCBpbmRleCBvZiB0aGUgRE1TTyBjb250cm9sIGF0IGVhY2ggdGltZSBwb2ludC4gVGhpcyBpcyBkb25lIGZvciBlYWNoIGluZGVwZW5kZW50IGV4cGVyaW1lbnQgdG8gbWFrZSB0aGVtIG1vcmUgY29tcGFyYWJsZSBhbmQgdG8gYWRkcmVzcyBwb3RlbnRpYWwgYmF0Y2ggZWZmZWN0cy4gCgoqIF9fcmVtb3ZlX2Rtc286X18KVGhpcyBmdW5jdGlvbiByZW1vdmVzIHRoZSBETVNPIGNvbnRyb2wgZnJvbSB0aGUgZGF0YXNldCBhZnRlciBsb2NhbCBub3JtYWxpemF0aW9uLgoKKiBfX3Njb3JlMS5mdW5jdGlvbjpfXwpUaGlzIGZ1bmN0aW9uIHRha2VzIHRoZSBkYXRhZnJhbWUgd2l0aCBiYXNpcyBzcGxpbmUgY29lZmZpY2llbnRzIGZvciBlYWNoIGNvbXBvdW5kIGFzIGFuIGFyZ3VtZW50IGFuZCBjYWxjdWxhdGVzIGEgZGlzdGFuY2UgbWF0cml4LiBUaGUgZGlzdGFuY2UgbWVhc3VyZSBjYW4gYmUgcHJvdmlkZWQgYXMgYXJndW1lbnQgdG8gdGhlIGZ1bmN0aW9uLiBUaGUgZGlzdGFuY2UgbWF0cml4IGlzIHNvcnRlZCBhbmQgZm9yIGVhY2ggcmVwbGljYXRlIGEgcmFuayBzdW0gaXMgY2FsY3VsYXRlZC4gQSBub3JtYWxpemVkIHNjb3JlIGlzIGNhbGN1bGF0ZWQgYnkgZGl2aXNpb24gb2YgdGhlIGlkZWFsIHNjb3JlIG9mIGEgc2V0IG9mIHJlcGxpY2F0ZXMgYnkgdGhlIG9idGFpbmVkIHNjb3JlLiBUaGUgY2xvc2VyIHRoaXMgdmFsdWUgaXMgdG8gMSB0aGUgYmV0dGVyIHRoZSByZXByb2R1Y2liaWxpdHkuIFRoZSBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCB3aXRoIHRoZSBjb21wb3VuZCBuYW1lcywgbnVtYmVyIG9mIHJlcGxpY2F0ZXMgcGVyIGdyb3VwLCBzY29yZSBhbmQgbm9ybWFsaXplZCBzY29yZS4KCgoKYGBge3J9CnNvdXJjZSgieENlbGxfZnVuY3Rpb25zLlIiKQpgYGAKCiMjIyMgUHJvY2VzcyB0aGUgMTIgeENlbGxpZ2VuY2UgcnVucyB3aXRoIHRoZSB4Y2VsbF9wcm9jZXNzX2RhdGEuUiBzY3JpcHQuClRoZSB4Y2VsbF9wcm9jZXNzX2RhdGEuUiBzY3JpcHRlIHByb2Nlc3NlcyB0aGUgMTIgZGF0YXNldHMgaW4gdGhlIGZvbGxvd2luZyBtYW5uZXI6IApUaGUgcmF3IGRhdGEgYXJlIHJlYWQgYW5kIHRoZSBkYXRhZnJhbWUgaXMgZWRpdGVkIHRvIGNvbnRhaW4gb25seSB0aGUgbGFzdCBtZWFzdXJlbWVudCBiZWZvcmUgY29tcG91bmQgYWRkaXRpb24gYW5kIDgwMCBtZWFzdXJlbWVudHMgYWZ0ZXIgY29tcG91bmQgYWRkaXRpb24uIFRoZW4gdGhlIGdsb2JhbCBub3JtYWxpemF0aW9uIGlzIHBlcmZvcm1lZCBhbmQgdGhlIGRvX21lZGlhbl9wb2xpc2ggZnVuY3Rpb24gaXMgYXBwbGllZCB0byBpZGVudGlmeSBvdXRsaWVycy4gT3V0bGllcnMgd2l0aCBhYnNvbHV0ZSByZXNpZHVhbCBzdW0gb2YgZ3JlYXRlciB0aGFuIDkwIGFyZSByZW1vdmVkLiBNZWRpYW4gVENSUHMgYXJlIGNhbGN1bGF0ZWQgYW5kIG5vcm1hbGl6ZWQgbG9jYWxseSBieSBzdWJ0cmFjdGluZyB0aGUgRE1TTyBjb250cm9sIFRDUlAuIEEgZGF0YWZyYW1lIHdpdGhvdXQgbm9ybWFsaXphdGlvbiB3aXRoIHRoZSBETVNPIGNvbnRyb2wgaXMgYWxzbyBnZW5lcmF0ZWQgZm9yIGNvbXBhcmlzb25zLgoKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpzb3VyY2UoInhjZWxsX3Byb2Nlc3NfZGF0YS5SIikKYGBgCiMjIyMgUGxvdCBmaWd1cmUgMgpgYGB7cn0KcGFyKG1mcm93ID0gYygxLDIpKQoKcGxvdCh4PXJvd25hbWVzKHhjZWxsX21lZGlhbl83KSwgeT14Y2VsbF9tZWRpYW5fN1ssIjdfQ3l0b2NoYWxhc2luRCJdLCB0eXBlPSJsIiwgY29sPSJibHVlIiwgbHdkID0gMiwKICAgICBtYWluID0gIkFjdGluIiwgY2V4Lm1haW49MSwgeWxpbT1jKDAsMyksIHlsYWI9Ik5DSSIsIHhsYWI9InQgW2hdIikKbGluZXMoeD1yb3duYW1lcyh4Y2VsbF9tZWRpYW5fNyksIHk9eGNlbGxfbWVkaWFuXzdbLCI3X0Nob25kcmFtaWRDIl0sIHR5cGU9ImwiLCBjb2w9ImdyZWVuIiwgbHdkID0gMikKbGVnZW5kKCJ0b3BsZWZ0IixsZWdlbmQ9YygiQ3l0b2NoYWxhc2luIEQiLCAiQ2hvbmRyYW1pZGUgQyIpLAogICAgICAgICBjb2w9IGMoImJsdWUiLCAiZ3JlZW4iKSxwY2g9YygxNiwxOCksYnR5PSJuIixuY29sPTEsY2V4PTAuOCxwdC5jZXg9MSkKCnBsb3QoeD1yb3duYW1lcyh4Y2VsbF9tZWRpYW5fNyksIHk9eGNlbGxfbWVkaWFuXzdbLCI3X01HMTMyIl0sIHR5cGU9ImwiLCBjb2w9InJlZCIsIGx3ZCA9IDIsIAogICAgIG1haW4gPSAiUHJvdGVhc29tZSIsIGNleC5tYWluPTEsIHlsaW09YygwLDMpLCB5bGFiPSJOQ0kiLCB4bGFiPSJ0IFtoXSIpCmxpbmVzKHg9cm93bmFtZXMoeGNlbGxfbWVkaWFuXzcpLCB5PXhjZWxsX21lZGlhbl83WywiN19Cb3J0ZXpvbWliIl0sIHR5cGU9ImwiLCBjb2w9InB1cnBsZSIsIGx3ZCA9IDIpCmxlZ2VuZCgidG9wbGVmdCIsbGVnZW5kPWMoIk1HMTMyIiwgIkJvcnRlem9taWIiKSwKICAgICAgIGNvbD0gYygiYmx1ZSIsICJncmVlbiIpLHBjaD1jKDE2LDE4KSxidHk9Im4iLG5jb2w9MSxjZXg9MC44LHB0LmNleD0xKQoKICAKYGBgCgoKIyMjIyBDb21iaW5lIGFsbCBtYXRyaWNlcyBjb250YWluaW5nIHRoZSBub3JtYWxpemVkIG1lZGlhbiBUQ1JQIGRhdGEgZnJvbSBlYWNoIHJ1biBpbiBvbmUgYmlnIG1hdHJpeCBhbmQgcmVvcmRlciB0aGUgY29sdW1uIG5hbWVzIGFscGhhYmV0aWNhbGx5CgoKYGBge3J9Cm1lZGlhbi5jb21iaW5lZCA8LSBjYmluZCh4Y2VsbF9tZWRpYW5fbm9ybV8xLCB4Y2VsbF9tZWRpYW5fbm9ybV8yLCB4Y2VsbF9tZWRpYW5fbm9ybV8zLAogICAgICAgICAgICAgICAgICAgICAgICAgeGNlbGxfbWVkaWFuX25vcm1fNCwgeGNlbGxfbWVkaWFuX25vcm1fNSwgeGNlbGxfbWVkaWFuX25vcm1fNiwKICAgICAgICAgICAgICAgICAgICAgICAgIHhjZWxsX21lZGlhbl9ub3JtXzcsIHhjZWxsX21lZGlhbl9ub3JtXzgsIHhjZWxsX21lZGlhbl9ub3JtXzksCiAgICAgICAgICAgICAgICAgICAgICAgICB4Y2VsbF9tZWRpYW5fbm9ybV8xMCwgeGNlbGxfbWVkaWFuX25vcm1fMTEsIHhjZWxsX21lZGlhbl9ub3JtXzEyLAogICAgICAgICAgICAgICAgICAgICAgICAgeGNlbGxfbWVkaWFuX25vcm1fMTMsIHhjZWxsX21lZGlhbl9ub3JtXzE0LCB4Y2VsbF9tZWRpYW5fbm9ybV8xNSwKICAgICAgICAgICAgICAgICAgICAgICAgIHhjZWxsX21lZGlhbl9ub3JtXzE2KQoKCmZvciAoaSBpbiAxIDogbGVuZ3RoKG1lZGlhbi5jb21iaW5lZFsxLF0pKXsKICB0ZW1wIDwtIHVubGlzdChzdHJzcGxpdCh0b1N0cmluZyhjb2xuYW1lcyhtZWRpYW4uY29tYmluZWQpW2ldKSwgIl8iKSkKICBjb2xuYW1lcyhtZWRpYW4uY29tYmluZWQpW2ldIDwtIHBhc3RlKHRlbXBbMl0sIHRlbXBbMV0sIHNlcD0iXyIpCn0KCgptZWRpYW4uY29tYmluZWQub3JkZXJlZCA8LSBtZWRpYW4uY29tYmluZWRbLG1peGVkb3JkZXIoY29sbmFtZXMobWVkaWFuLmNvbWJpbmVkKSldCgoKCmBgYAoKIyMjIyBHZW5lcmF0ZSBhbiBhbmFsb2dvdXMgbWF0cml4IHdpdGhvdXQgdGhlIERNU08gbm9ybWFsaXphdGlvbgoKYGBge3J9Cm1lZGlhbi5jb21iaW5lZF9ub3Rub3JtIDwtIGNiaW5kKAogICAgICAgICAgICAgICAgICAgICAgICAgeGNlbGxfbWVkaWFuX25vdG5vcm1fMSwgeGNlbGxfbWVkaWFuX25vdG5vcm1fMiwgeGNlbGxfbWVkaWFuX25vdG5vcm1fMywKICAgICAgICAgICAgICAgICAgICAgICAgIHhjZWxsX21lZGlhbl9ub3Rub3JtXzQsIHhjZWxsX21lZGlhbl9ub3Rub3JtXzUsIHhjZWxsX21lZGlhbl9ub3Rub3JtXzYsCiAgICAgICAgICAgICAgICAgICAgICAgICB4Y2VsbF9tZWRpYW5fbm90bm9ybV83LCB4Y2VsbF9tZWRpYW5fbm90bm9ybV84LCB4Y2VsbF9tZWRpYW5fbm90bm9ybV85LAogICAgICAgICAgICAgICAgICAgICAgICAgeGNlbGxfbWVkaWFuX25vdG5vcm1fMTAsIHhjZWxsX21lZGlhbl9ub3Rub3JtXzExLCB4Y2VsbF9tZWRpYW5fbm90bm9ybV8xMiwKICAgICAgICAgICAgICAgICAgICAgICAgIHhjZWxsX21lZGlhbl9ub3Rub3JtXzEzLCB4Y2VsbF9tZWRpYW5fbm90bm9ybV8xNCwgeGNlbGxfbWVkaWFuX25vdG5vcm1fMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICB4Y2VsbF9tZWRpYW5fbm90bm9ybV8xNikKCmZvciAoaSBpbiAxIDogbGVuZ3RoKG1lZGlhbi5jb21iaW5lZF9ub3Rub3JtWzEsXSkpewogIHRlbXAgPC0gdW5saXN0KHN0cnNwbGl0KHRvU3RyaW5nKGNvbG5hbWVzKG1lZGlhbi5jb21iaW5lZF9ub3Rub3JtKVtpXSksICJfIikpCiAgY29sbmFtZXMobWVkaWFuLmNvbWJpbmVkX25vdG5vcm0pW2ldIDwtIHBhc3RlKHRlbXBbMl0sIHRlbXBbMV0sIHNlcD0iXyIpCn0KCm1lZGlhbi5jb21iaW5lZF9ub3Rub3JtLm9yZGVyZWQgPC0gCiAgbWVkaWFuLmNvbWJpbmVkX25vdG5vcm1bLG1peGVkb3JkZXIoY29sbmFtZXMobWVkaWFuLmNvbWJpbmVkX25vdG5vcm0pKV0KCmBgYAoKCiMjIyMgQ2FsY3VsYXRlIHNtb290aGluZyBzcGxpbmVzCgoKYGBge3J9Cm5ld3Jvd25hbWVzIDwtIHJlYWQuZGVsaW0oIm5ld3Jvd25hbWVzLnR4dCIsIGhlYWRlcj1GLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojc21vb3RoaW5nIHNwbGluZXMgd2l0aCBuZXcgcm93bmFtZXMKbWVkaWFuLnNwPC1tYXRyaXgobmNvbD0yMiwgbnJvdz0yMTkpCnJvdy5uYW1lcyhtZWRpYW4uc3ApPC1uZXdyb3duYW1lcyRWMQp0PC1yb3duYW1lcyhtZWRpYW4uY29tYmluZWQub3JkZXJlZCkKdDwtYXMubnVtZXJpYyh0KQoKaTwtMApyZXBlYXR7CiAgaTwtaSsxCiAgdGVtcDwtc21vb3RoLnNwbGluZSh4PXQsIHk9IG1lZGlhbi5jb21iaW5lZC5vcmRlcmVkWyxpXSwgbmtub3RzPTIwKQogIG1lZGlhbi5zcFtpLF08LXRlbXAkZml0JGNvZWYKICBpZiAoaT09MjE5KSBicmVhawp9CgojYW5hbG9nb3VzIGZvciB0aGUgZGF0YSB3aXRob3V0IGxvY2FsIG5vcm1hbGl6YXRpb24KbWVkaWFuLnNwLm5vdG5vcm08LW1hdHJpeChuY29sPTIyLCBucm93PTIxOSkKcm93Lm5hbWVzKG1lZGlhbi5zcC5ub3Rub3JtKTwtbmV3cm93bmFtZXMkVjEKdDwtcm93bmFtZXMobWVkaWFuLmNvbWJpbmVkX25vdG5vcm0ub3JkZXJlZCkKdDwtYXMubnVtZXJpYyh0KQoKaTwtMApyZXBlYXR7CiAgaTwtaSsxCiAgdGVtcDwtc21vb3RoLnNwbGluZSh4PXQsIHk9IG1lZGlhbi5jb21iaW5lZF9ub3Rub3JtLm9yZGVyZWRbLGldLCBua25vdHM9MjApCiAgbWVkaWFuLnNwLm5vdG5vcm1baSxdPC10ZW1wJGZpdCRjb2VmCiAgaWYgKGk9PTIxOSkgYnJlYWsKfQoKYGBgCgojIyMjIENhbGN1bGF0ZSBzY29yZSBmb3IgYmlvbG9naWNhbCByZXBsaWNhdGVzCgpUaGUgZ29hbCBpcyB0byBldmFsdXRhdGUgcmVwcm9kdWNpYmlsaXR5IGZvciBlYWNoIGNvbXBvdW5kIGFuZCB0byBqdWRnZSB3aGljaCBjb21wb3VuZHMgYXJlIHdlbGwgcmVwcm9kdWNpYmxlIGFuZCB3aGljaCBub3Qgc28gbXVjaC4gRm9yIHRoaXMgcHVycG9zZSBhIHJhbmstYmFzZWQgc2NvcmUgaXMgY2FsY3VsYXRlZCBmb3IgZWFjaCBjb21wb3VuZCwgdGhlIGNsb3NlciB0byBvbmUgdGhlIGJldHRlci4KSW4gYWRkaXRpb24gYW4gb3ZlcmFsbCBzY29yZSBpcyBjYWxjdWxhdGVkLCB3aGljaCBpcyBhIHNpbmdsZSBudW1iZXIgdG8ganVkZ2UgaG93IHRoZSBleHBlcmltZW50cyBhbmQgdGhlIGRhdGEgYW5hbHlzaXMgb3ZlcmFsbCBwZXJmb3JtZWQuIEFnYWluIGl0IGlzIGEgcmFuay1iYXNlZCBzY29yZSwgdGhlIGNsb3NlciB0byBvbmUgdGhlIGJldHRlci4KCkhlcmUgd2UgYWxzbyB3YW50IHRvIG9wdGltaXplIGNlcnRhaW4gcGFyYW1ldGVycyBmb3IgdGhlIGRhdGEgYW5hbHlzaXMsIG5hbWVseSB0aGUgZGlzdGFuY2UgbWVhc3N1cmUgYW5kIGRhdGEgc2NhbGluZyAvIGNlbnRlcmluZy4KCkRpc3RhbmNlIG1lYXN1cmVzIChzb3VyY2U6IGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9zdGF0cy9odG1sL2Rpc3QuaHRtbCkKY29tcGFyZWQgYXJlICh3cml0dGVuIGZvciB0d28gdmVjdG9ycyB4IGFuZCB5KToKCmV1Y2xpZGVhbjoKVXN1YWwgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIHZlY3RvcnMgKDIgbm9ybSBha2EgTF8yKSwgc3FydChzdW0oKHhfaSAtIHlfaSleMikpLgoKbWF4aW11bToKTWF4aW11bSBkaXN0YW5jZSBiZXR3ZWVuIHR3byBjb21wb25lbnRzIG9mIHggYW5kIHkgKHN1cHJlbXVtIG5vcm0pCgptYW5oYXR0YW46CkFic29sdXRlIGRpc3RhbmNlIGJldHdlZW4gdGhlIHR3byB2ZWN0b3JzICgxIG5vcm0gYWthIExfMSkuCgpTY2FsaW5nIGFuZCBDZW50ZXJpbmcgb2YgdGhlIG1hdHJpY2VzIHVzaW5nIHRoZSBzY2FsZSBmdW5jdGlvbiBvZiBSIGJhc2U6ClIgZG9jdW1lbnRhdGlvbjoKIlRoZSB2YWx1ZSBvZiBjZW50ZXIgZGV0ZXJtaW5lcyBob3cgY29sdW1uIGNlbnRlcmluZyBpcyBwZXJmb3JtZWQuIElmIGNlbnRlciBpcyBhIG51bWVyaWMgdmVjdG9yIHdpdGggbGVuZ3RoIGVxdWFsIHRvIHRoZSBudW1iZXIgb2YgY29sdW1ucyBvZiB4LCB0aGVuIGVhY2ggY29sdW1uIG9mIHggaGFzIHRoZSBjb3JyZXNwb25kaW5nIHZhbHVlIGZyb20gY2VudGVyIHN1YnRyYWN0ZWQgZnJvbSBpdC4gSWYgY2VudGVyIGlzIFRSVUUgdGhlbiBjZW50ZXJpbmcgaXMgZG9uZSBieSBzdWJ0cmFjdGluZyB0aGUgY29sdW1uIG1lYW5zIG9mIHggZnJvbSB0aGVpciBjb3JyZXNwb25kaW5nIGNvbHVtbnMsIGFuZCBpZiBjZW50ZXIgaXMgRkFMU0UsIG5vIGNlbnRlcmluZyBpcyBkb25lLgoKVGhlIHZhbHVlIG9mIHNjYWxlIGRldGVybWluZXMgaG93IGNvbHVtbiBzY2FsaW5nIGlzIHBlcmZvcm1lZCAoYWZ0ZXIgY2VudGVyaW5nKS4gSWYgc2NhbGUgaXMgYSBudW1lcmljIHZlY3RvciB3aXRoIGxlbmd0aCBlcXVhbCB0byB0aGUgbnVtYmVyIG9mIGNvbHVtbnMgb2YgeCwgdGhlbiBlYWNoIGNvbHVtbiBvZiB4IGlzIGRpdmlkZWQgYnkgdGhlIGNvcnJlc3BvbmRpbmcgdmFsdWUgZnJvbSBzY2FsZS4gSWYgc2NhbGUgaXMgVFJVRSB0aGVuIHNjYWxpbmcgaXMgZG9uZSBieSBkaXZpZGluZyB0aGUgKGNlbnRlcmVkKSBjb2x1bW5zIG9mIHggYnkgdGhlaXIgcm9vdC1tZWFuLXNxdWFyZSwgYW5kIGlmIHNjYWxlIGlzIEZBTFNFLCBubyBzY2FsaW5nIGlzIGRvbmUuCgpUaGUgcm9vdC1tZWFuLXNxdWFyZSBmb3IgYSBjb2x1bW4gaXMgb2J0YWluZWQgYnkgY29tcHV0aW5nIHRoZSBzcXVhcmUtcm9vdCBvZiB0aGUgc3VtLW9mLXNxdWFyZXMgb2YgdGhlIG5vbi1taXNzaW5nIHZhbHVlcyBpbiB0aGUgY29sdW1uIGRpdmlkZWQgYnkgdGhlIG51bWJlciBvZiBub24tbWlzc2luZyB2YWx1ZXMgbWludXMgb25lLiIKCmBgYHtyLCBlY2hvPVRSVUV9CiNjcml0ZXJpb24gaG93IHRoZSBvdmVyYWxsIHByb2NlZHVyZSBzY29yZWQgCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLCAiZXVjbGlkZWFuIikKZXVjbGlkZWFuIDwtIHN1bShyZXMkbm9ybXNjb3JlKS9pCgptZWRpYW4uc3Auc2NhbGVkIDwtIHNjYWxlKG1lZGlhbi5zcCwgY2VudGVyID0gRkFMU0UsIHNjYWxlID0gVFJVRSkKcmVzIDwtIHNjb3JlMS5mdW5jdGlvbihtZWRpYW4uc3Auc2NhbGVkLCAiZXVjbGlkZWFuIikKZXVjbGlkZWFuLnNjYWxlZCA8LSBzdW0ocmVzJG5vcm1zY29yZSkvaSAKCm1lZGlhbi5zcC5jZW50ZXJlZC5zY2FsZWQgPC0gc2NhbGUobWVkaWFuLnNwLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLmNlbnRlcmVkLnNjYWxlZCwgImV1Y2xpZGVhbiIpCmV1Y2xpZGVhbi5jZW50ZXJlZC5zY2FsZWQgPC0gc3VtKHJlcyRub3Jtc2NvcmUpL2kgCiMjIwoKcmVzIDwtIHNjb3JlMS5mdW5jdGlvbihtZWRpYW4uc3AsICJtYXhpbXVtIikKbWF4aW11bSA8LSBzdW0ocmVzJG5vcm1zY29yZSkvaQoKcmVzIDwtIHNjb3JlMS5mdW5jdGlvbihtZWRpYW4uc3Auc2NhbGVkLCAibWF4aW11bSIpCm1heGltdW0uc2NhbGVkIDwtIHN1bShyZXMkbm9ybXNjb3JlKS9pIAoKcmVzIDwtIHNjb3JlMS5mdW5jdGlvbihtZWRpYW4uc3AuY2VudGVyZWQuc2NhbGVkLCAibWF4aW11bSIpCm1heGltdW0uY2VudGVyZWQuc2NhbGVkIDwtIHN1bShyZXMkbm9ybXNjb3JlKS9pCiMjIwoKcmVzIDwtIHNjb3JlMS5mdW5jdGlvbihtZWRpYW4uc3AsICJtYW5oYXR0YW4iKQptYW5oYXR0YW4gPC0gc3VtKHJlcyRub3Jtc2NvcmUpL2kKCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLnNjYWxlZCwgIm1hbmhhdHRhbiIpCm1hbmhhdHRhbi5zY2FsZWQgPC0gc3VtKHJlcyRub3Jtc2NvcmUpL2kKCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLmNlbnRlcmVkLnNjYWxlZCwgIm1hbmhhdHRhbiIpCm1hbmhhdHRhbi5jZW50ZXJlZC5zY2FsZWQgPC0gc3VtKHJlcyRub3Jtc2NvcmUpL2kKIyMjCgpub3Rfc2NhbGVkIDwtIGMoZXVjbGlkZWFuLCBtYXhpbXVtLCBtYW5oYXR0YW4pCnNjYWxlZCA8LSBjKGV1Y2xpZGVhbi5zY2FsZWQsIG1heGltdW0uc2NhbGVkLCBtYW5oYXR0YW4uc2NhbGVkKQpjZW50X3NjYWxlZCA8LSBjKGV1Y2xpZGVhbi5jZW50ZXJlZC5zY2FsZWQsIG1heGltdW0uY2VudGVyZWQuc2NhbGVkLCBtYW5oYXR0YW4uY2VudGVyZWQuc2NhbGVkKQoKdGFiMSA8LSByYmluZChub3Rfc2NhbGVkLCBzY2FsZWQsIGNlbnRfc2NhbGVkKQpjb2xuYW1lcyh0YWIxKSA8LSBjKCJldWNsaWRlYW4iLCAibWF4aW11bSIsICJtYW5oYXR0YW4iKQp0YWIxIDwtIGFzLmRhdGEuZnJhbWUodGFiMSkKCiNncmlkLnRhYmxlKHJvdW5kKHRhYjEsIDMpKQprYWJsZShyb3VuZCh0YWIxLCAzKSwgY2FwdGlvbiA9ICJzY29yZXMgZm9yIDUgZGlzdGFuY2UgbWVhc3VyZXMgKy8tIHNjYWxpbmcgYW5kIGNlbnRlcmluZyIpCmBgYAoKKlJlc3VsdDogRXVjbGlkZWFuIGRpc3RhbmNlIHdpdGggc2NhbGVkIChub3QgY2VudHJlZCkgZGF0YSBwZXJmb3JtZWQgYmVzdC4KCk5vdyB3ZSB3YW50IHRvIGludmVzdGlnYXRlIGlmIHRoZSBsb2NhbCBub3JtYWxpemF0aW9uIHdpdGggdGhlIFRDUlAgZnJvbSBETVNPIHRyZWF0ZWQgY2VsbHMgZm9yIGVhY2ggcnVuIGxlYWQgdG8gYW4gaW1wcm92ZW1lbnQgb2YgcmVwcm9kdWNpYmlsaXR5LiAKCmBgYHtyfQptZWRpYW4uc3Aubm90bm9ybS5zY2FsZWQgPC0gc2NhbGUobWVkaWFuLnNwLm5vdG5vcm0sIGNlbnRlciA9IEZBTFNFLCBzY2FsZSA9IFRSVUUpCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLm5vdG5vcm0uc2NhbGVkLCAiZXVjbGlkZWFuIikKZXVjbGlkZWFuLnNjYWxlZC5ub3Rub3JtIDwtIHN1bShyZXMkbm9ybXNjb3JlKS9pIAoKbWVkaWFuLnNwLnNjYWxlZCA8LSBzY2FsZShtZWRpYW4uc3AsIGNlbnRlciA9IEZBTFNFLCBzY2FsZSA9IFRSVUUpCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLnNjYWxlZCwgImV1Y2xpZGVhbiIpCmV1Y2xpZGVhbi5zY2FsZWQgPC0gc3VtKHJlcyRub3Jtc2NvcmUpL2kgCmNhdChwYXN0ZTAoIndpdGhvdXQgbG9jYWwgbm9ybWFsaXphdGlvbjogIiwgcm91bmQoZXVjbGlkZWFuLnNjYWxlZC5ub3Rub3JtLCAzKSwKICAgICAgICAgICAiXG53aXRoIGxvY2FsIG5vcm1hbGl6YXRpb246ICIsIHJvdW5kKGV1Y2xpZGVhbi5zY2FsZWQsMykpKQoKYGBgCkNsZWFybHkgdGhlIGxvY2FsIG5vcm1hbGl6YXRpb24gaGFzIGxlYWQgdG8gYSBzdHJvbmcgaW1wcm92ZW1lbnQ6IDAuNDc5ID4+IDAuMjc0LgoKCkV2YWx1YXRlIHJlcHJvZHVjaWJpbGl0eSBvZiBiaW9sb2dpY2FsIHJlcGxpY2F0ZXMgZ3JvdXAtd2lzZSwgY2FsY3VsYXRlIGEgc2NvcmUgZm9yIGVhY2ggZ3JvdXAgb2YgcmVwbGljYXRlcwpgYGB7cn0KcmVzIDwtIHNjb3JlMS5mdW5jdGlvbihtZWRpYW4uc3AsICJldWNsaWRlYW4iKQpncm91cG1hdGNoIDwtIHJlYWQuZGVsaW0oImdyb3VwbWF0Y2gudHh0IiwgaGVhZGVyPUYpJFYxCmdyb3VwLnNjb3JlIDwtIGMoKQoKZm9yKGkgaW4gMTpsZW5ndGgoZ3JvdXBtYXRjaCkpewogIAogIG1hIDwtIGdyZXAoZ3JvdXBtYXRjaFtpXSwgcmVzJHJlcCkKICBnc2NvcmUgPC0gc3VtKHJlcyRub3Jtc2NvcmVbbWFdKS9sZW5ndGgobWEpCiAgZ3JvdXAuc2NvcmUgPC0gYyhncm91cC5zY29yZSwgZ3Njb3JlKQogIAp9Cmdyb3VwLnJlc3VsdCA8LSBkYXRhLmZyYW1lKGdyb3VwbWF0Y2gsZ3JvdXAuc2NvcmUpCmdyb3VwLnJlc3VsdCA8LSBncm91cC5yZXN1bHRbb3JkZXIoLWdyb3VwLnJlc3VsdCRncm91cC5zY29yZSksXQprYWJsZShncm91cC5yZXN1bHQpCndyaXRlLmNzdjIoZ3JvdXAucmVzdWx0LCBmaWxlID0gImdyb3VwX3Jlc3VsdHMuY3N2IikKCmBgYAoKCldoYXQgd2UgY2FuIGRvIG5vdyBpcyB0byB1c2UgdGhlIHNjb3JlIGNhbGN1bGF0ZWQgZm9yIGVhY2ggYmlvbG9naWNhbCByZXBsaWNhdGUgYW5kIGRlZmluZSBhIHRocmVzaG9sZCB0byByZW1vdmUgcmVwbGljYXRlcyB3aGljaCBhcmUgb3V0bGllcnMuIEFuZCB0aGVuIGNhbGN1bGF0ZSB0aGUgZ3JvdXB3aXNlIHNjb3JlcyBhbmQgdGhlIG92ZXJhbGwgc2NvcmUgYWdhaW4gdG8gY2hlY2sgdGhlIGltcHJvdmVtZW50LgoKCmBgYHtyfQojRmlsdGVyIHJlZmVyZW5jZSBzZXQKI25vcm1haWx6ZWQgc2NvcmUgPCAwLjEgaXMgZGVmaW5lZCBhcyBvdXRsaWVyCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLCAiZXVjbGlkZWFuIikKCm15X2hpdGxpc3QgPC0gcmVzJHJlcFtyZXMkbm9ybXNjb3JlIDwgMC4xXQpteV9vdXRsaWVycyA8LSBjKCkKCmZvciAoaSBpbiAxOiBsZW5ndGgobXlfaGl0bGlzdCkpIHsKdGVtcCA8LSB1bmxpc3Qoc3Ryc3BsaXQodG9TdHJpbmcobXlfaGl0bGlzdFtpXSksICJfIikpCm5ld19uYW1lIDwtIHBhc3RlMCh0ZW1wWzJdLCAiXyIsIHRlbXBbM10pCm15X291dGxpZXJzIDwtIGMobXlfb3V0bGllcnMsIG5ld19uYW1lKQp9CgpwcmludChteV9vdXRsaWVycykKCmBgYAoKCgpQbG90IHRoZSBiaW9sb2dpY2FsIHJlcGxpY2F0ZXMgb2YgZWFjaCBjb21wb3VuZCAoZ2VuZXJhdGVkIGJ5IHRoZSBtZWRpYW5zIG9mIHRoZSB0ZWNobmljYWwgcmVwbGljYXRlcyBvbiBlYWNoIEUtcGxhdGUpIHdpdGggYW5kIHdpdGhvdXQgbG9jYWwgbm9ybWFsaXphdGlvbi4KCgpgYGB7cn0KI3BkZihmaWxlID0gIm1lZGlhbl9UQ1JQLnBkZiIsIHBhcGVyID0gImE0IikKCnBhcihtZnJvdyA9IGMoMSwyKSkKaTwtMApyZXBlYXR7CiAgaTwtaSsxCiAgCiAgCiAgbWE8LWdyZXAoZ3JvdXBtYXRjaFtpXSwgY29sbmFtZXMobWVkaWFuLmNvbWJpbmVkX25vdG5vcm0ub3JkZXJlZCkpIAogIHo8LW1lZGlhbi5jb21iaW5lZF9ub3Rub3JtLm9yZGVyZWRbLG1hXQogIHo8LWFzLmRhdGEuZnJhbWUoeikKICB6PC16WyxtaXhlZG9yZGVyKG5hbWVzKHopKV0KICAKICAKICBwbG90KHg9cm93bmFtZXMoeiksIHk9elssMV0sIHR5cGU9ImwiLCBjb2w9ImJsdWUiLCBtYWluID0gIm5vdCBub3JtYWxpemVkIiwKICAgICAgIGNleC5tYWluPTAuOCwgeWxpbT1jKDAsMyksIHlsYWI9Ik5DSSIsIHhsYWI9InQgW2hdIikKICAKICAKICAKICBteUMgPC0gbGVuZ3RoKG1hKQogIG15Q29sb3IgPC0gYygiZ3JlZW4iLCAicmVkIiwgImJsYWNrIiwgIm9yYW5nZSIpCiAgCiAgZm9yIChuIGluIDE6KG15Qy0xKSkKICB7CiAgICBsaW5lcyh4PXJvd25hbWVzKHopLCB5PXpbLG4rMV0sIHR5cGU9ImwiLCBjb2w9bXlDb2xvcltuXSkKICB9CiAgbGVnZW5kKCJ0b3BsZWZ0IixsZWdlbmQ9Y29sbmFtZXMoeiksCiAgICAgICAgIGNvbD0gYygiYmx1ZSIsIG15Q29sb3IpLHBjaD1jKDE2LDE4KSxidHk9Im4iLG5jb2w9MSxjZXg9MC42LHB0LmNleD0wLjcpCiAgCiAgejwtbWVkaWFuLmNvbWJpbmVkLm9yZGVyZWRbLG1hXQogIHo8LWFzLmRhdGEuZnJhbWUoeikKICB6PC16WyxtaXhlZG9yZGVyKG5hbWVzKHopKV0KICAKICAKICBwbG90KHg9cm93bmFtZXMoeiksIHk9elssMV0sIHR5cGU9ImwiLCBjb2w9ImJsdWUiLCBtYWluID0gIm5vcm1hbGl6ZWQiLCAKICAgICAgIGNleC5tYWluPTAuOCwgeWxpbT1jKC0xLjUsMS41KSwgeWxhYj0iTkNJIiwgeGxhYj0idCBbaF0iKQogIAogIAogIGZvciAobiBpbiAxOihteUMtMSkpCiAgewogICAgbGluZXMoeD1yb3duYW1lcyh6KSwgeT16WyxuKzFdLCB0eXBlPSJsIiwgY29sPW15Q29sb3Jbbl0pCiAgfQogIGxlZ2VuZCgidG9wbGVmdCIsbGVnZW5kPWNvbG5hbWVzKHopLAogICAgICAgICBjb2w9IGMoImJsdWUiLCBteUNvbG9yKSxwY2g9YygxNiwxOCksYnR5PSJuIixuY29sPTEsY2V4PTAuNixwdC5jZXg9MC43KQogIAogIAogIAogIAogIGlmIChpPT02MCkgYnJlYWsKfQoKCiNkZXYub2ZmKCkKYGBgCgpJbiB0aGUgY2FzZSB3aGVyZSBtb3JlIHRoYW4gb25lIGJpb2xvZ2ljYWwgcmVwbGljYXRlIG9yIG9uZSBjb21wb3VuZCB3YXMgZm91bmQgdG8gYmUgYmVsb3cgdGhlIHRocmVzaG9sZCwgdGhlIG9uZSB0aGF0IGRldmlhdGVzIHRoZSBtb3N0IGlzIHJlbW92ZWQgZnJvbSB0aGUgZGF0YS4KCmBgYHtyfQpteV9vdXRsaWVyc19zZWxlY3RlZCA8LSBjKCJBcGljdWxhcmVuXzEiLCAiQXJjaGF6b2xpZEJfMTMiLCAiQXJneXJpbkFfNCIsICJDYW1wdG90aGVjaW5fNCIsICJDb2xjaGljaW5lXzE2IiwgIkNydWVudGFyZW5BXzE0IiwgIkRveG9ydWJpY2luXzkiLCAiRXBvdGhpbG9uZUJfMTMiLCAiRXRvcG9zaWRlXzMiLCAiR2VwaHlyb25pY0FjaWRBXzkiLCAiSW5kaXJ1YmluM21vbm94aW1lXzMiLCAiTFkyOTQwMDJfMiIsICJNZXRob3RyZXhhdGVfMSIsICJNRzEzMl8xNiIsICJNeXJpYXBvcm9uZV8zIiwgIk5lb3BlbHRvbGlkZV8xNiIsICJOb2NvZGF6b2xlXzE1IiwgIk9rYWRhaWNBY2lkXzIiLCAiT2xpZ29teWNpbl8xNSIsICJPeGFtZmxhdGluXzE2IiwgIlBEMTY5MzE2XzkiLCAiUmF0amFkb25DXzkiLCAiU2ltdmFzdGF0aW5fOSIsICJUYXhvbF82IiwgIlRyaWNob3N0YXRpbl80IiwgIkJvcnRlem9taWJfOSIsICJWaW9wcm9saWRlXzEwIikKCm1hIDwtIG1hdGNoKG15X291dGxpZXJzX3NlbGVjdGVkLCBjb2xuYW1lcyhtZWRpYW4uY29tYmluZWQub3JkZXJlZCkpCm1lZGlhbi5jb21iaW5lZC5lZGl0ZWQgPC0gbWVkaWFuLmNvbWJpbmVkLm9yZGVyZWRbLCAtbWFdCgoKI2NhbGN1bGF0ZSBjdWJpYyBzbW9vdGhpbmcgc3BsaW5lcwoKbWVkaWFuLnNwLmVkaXRlZDwtbWF0cml4KG5jb2w9MjIsIG5yb3c9MTkyKQpyb3cubmFtZXMobWVkaWFuLnNwLmVkaXRlZCk8LW5ld3Jvd25hbWVzJFYxWy1tYV0KdDwtcm93bmFtZXMobWVkaWFuLmNvbWJpbmVkLmVkaXRlZCkKdDwtYXMubnVtZXJpYyh0KQoKaTwtMApyZXBlYXR7CiAgaTwtaSsxCiAgdGVtcDwtc21vb3RoLnNwbGluZSh4PXQsIHk9IG1lZGlhbi5jb21iaW5lZC5lZGl0ZWRbLGldLCBua25vdHM9MjApCiAgbWVkaWFuLnNwLmVkaXRlZFtpLF08LXRlbXAkZml0JGNvZWYKICBpZiAoaT09MTkyKSBicmVhawp9CgoKCm1lZGlhbi5zcC5lZGl0ZWQuc2NhbGVkIDwtIHNjYWxlKG1lZGlhbi5zcC5lZGl0ZWQsIGNlbnRlciA9IEZBTFNFLCBzY2FsZSA9IFRSVUUpCnJlcyA8LSBzY29yZTEuZnVuY3Rpb24obWVkaWFuLnNwLmVkaXRlZC5zY2FsZWQsICJldWNsaWRlYW4iKQpldWNsaWRlYW4uc2NhbGVkIDwtIHN1bShyZXMkbm9ybXNjb3JlKS9pIApjYXQocGFzdGUwKCJFdWNsaWRlYW4uc2NhbGVkIGFmdGVyIHJlbW92YWwgb2Ygb3V0bGllcnM6ICIsIHJvdW5kKGV1Y2xpZGVhbi5zY2FsZWQsIDMpKSkKCgpncm91cC5zY29yZSA8LSBjKCkKCmZvcihpIGluIDE6bGVuZ3RoKGdyb3VwbWF0Y2gpKXsKICAKICBtYSA8LSBncmVwKGdyb3VwbWF0Y2hbaV0sIHJlcyRyZXApCiAgZ3Njb3JlIDwtIHN1bShyZXMkbm9ybXNjb3JlW21hXSkvbGVuZ3RoKG1hKQogIGdyb3VwLnNjb3JlIDwtIGMoZ3JvdXAuc2NvcmUsIGdzY29yZSkKICAKfQpncm91cC5yZXN1bHQgPC0gZGF0YS5mcmFtZShncm91cG1hdGNoLGdyb3VwLnNjb3JlKQpncm91cC5yZXN1bHQgPC0gZ3JvdXAucmVzdWx0W29yZGVyKC1ncm91cC5yZXN1bHQkZ3JvdXAuc2NvcmUpLF0Ka2FibGUoZ3JvdXAucmVzdWx0KQp3cml0ZS5jc3YyKGdyb3VwLnJlc3VsdCwgImdyb3VwX3Jlc3VsdF9maWx0ZXIuY3N2IikKYGBgCgoKV2l0aCB0aGUgaW1wcm92ZWQgZGF0YSB0aGUgbWVkaWFucyBvZiB0aGUgYmlvbG9naWNhbCByZXBsaWNhdGVzIGlzIGNhbGN1bGF0ZWQuCgpgYGB7cn0KI0NhbGN1bGF0ZSBtZWRpYW5zIG9mIG1lZGlhbnMKbWVkaWFuLmNvbWJpbmVkLm1lZGlhbjwtbWF0cml4KG5jb2w9NjAsIG5yb3c9ODAwKQpjb2xuYW1lcyhtZWRpYW4uY29tYmluZWQubWVkaWFuKTwtZ3JvdXBtYXRjaApyb3duYW1lcyhtZWRpYW4uY29tYmluZWQubWVkaWFuKTwtcm93Lm5hbWVzKG1lZGlhbi5jb21iaW5lZC5lZGl0ZWQpCmk8LTAKcmVwZWF0ewogIGk8LWkrMQogIAogIG5hbWU8LWdyb3VwbWF0Y2hbaV0KICBtYTwtZ3JlcChncm91cG1hdGNoW2ldLCBjb2xuYW1lcyhtZWRpYW4uY29tYmluZWQuZWRpdGVkKSkKICB6PC1tZWRpYW4uY29tYmluZWQuZWRpdGVkWyxtYV0KICBtZWRpYW4uY29tYmluZWQubWVkaWFuWyxpXTwtYXBwbHkoeiwgMSwgbWVkaWFuKSAKICAKICBpZiAoaT09NjApIGJyZWFrCn0KCgojc21vb3RoaW5nIHNwbGluZXMgZm9yIG1lZGlhbi5jb21iaW5lZAp4bWVkaWFuLmNvbWJpbmVkPC1tYXRyaXgobmNvbD0yMiwgbnJvdz02MCkKcm93Lm5hbWVzKHhtZWRpYW4uY29tYmluZWQpPC1jb2xuYW1lcyhtZWRpYW4uY29tYmluZWQubWVkaWFuKQp0PC1yb3duYW1lcyhtZWRpYW4uY29tYmluZWQubWVkaWFuKQp0PC1hcy5udW1lcmljKHQpCgoKaTwtMApyZXBlYXR7CiAgaTwtaSsxCiAgdGVtcDwtc21vb3RoLnNwbGluZSh4PXQsIHk9IG1lZGlhbi5jb21iaW5lZC5tZWRpYW5bLGldLCBua25vdHM9MjApCiAgeG1lZGlhbi5jb21iaW5lZFtpLF08LXRlbXAkZml0JGNvZWYKICBpZiAoaT09NjApIGJyZWFrCn0KCgojU2NhbGluZwp4bWVkaWFuLmNvbWJpbmVkLnNjYWxlZCA8LSBzY2FsZSh4bWVkaWFuLmNvbWJpbmVkLCBzY2FsZSA9IFRSVUUsIGNlbnRlciA9IEZBTFNFKQpjb2xuYW1lcyh4bWVkaWFuLmNvbWJpbmVkLnNjYWxlZCkgPC0gYygiYzEiLCAiYzIiLCAiYzMiLCAiYzQiLCAiYzUiLCAiYzYiLCAiYzciLCAiYzgiLCAiYzkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYzEwIiwgImMxMSIsICJjMTIiLCAiYzEzIiwgImMxNCIsICJjMTUiLCAiYzE2IiwgImMxNyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjMTgiLCAiYzE5IiwgImMyMCIsICJjMjEiLCAiYzIyIikKCiMjZGlzdG1hdCBhbmQgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKeG1lZGlhbi5jb21iaW5lZC5kaXN0bWF0IDwtIGRpc3QoeG1lZGlhbi5jb21iaW5lZC5zY2FsZWQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQp4bWVkaWFuLmhjbHVzdC5zb3J0ZWQgPC0gZGVuZHNvcnQoaGNsdXN0KHhtZWRpYW4uY29tYmluZWQuZGlzdG1hdCwgbWV0aG9kID0gImNvbXBsZXRlIikpCnBhcihjZXggPSAwLjYpCnBsb3QoYXMuZGVuZHJvZ3JhbSh4bWVkaWFuLmhjbHVzdC5zb3J0ZWQpKQoKCiNoZWF0bWFwCm15X2NvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbChuID0gMTAsIG5hbWUgPSAiUmRZbEJ1IikpKSgxMDApCnBoZWF0bWFwKHhtZWRpYW4uY29tYmluZWQuc2NhbGVkLCBjbHVzdGVyX3Jvd3MgPSB4bWVkaWFuLmhjbHVzdC5zb3J0ZWQsIGNsdXN0ZXJfY29scyA9IFRSVUUsCiAgICAgICAgIGNvbG9yID0gbXlfY29sb3IsIGZvbnRzaXplID0gNS4wKQoKCmBgYAoKUmFuay1iYXNlZCBNb0EgcHJlZGljdGlvbgoKYGBge3J9CnhtZWRpYW4uY29tYmluZWQuc2NhbGVkIDwtIHNjYWxlKHhtZWRpYW4uY29tYmluZWQpCm15ZGlzdG1hdCA8LSBkaXN0KHhtZWRpYW4uY29tYmluZWQuc2NhbGVkLCBtZXRob2QgPSAiZXVjbGlkZWFuIikKbXlkaXN0bWF0IDwtIGFzLm1hdHJpeChteWRpc3RtYXQpCnJhbmsucHJlZGljdCA8LSBtYXRyaXgobmNvbD02MCwgbnJvdz02MCkKY29sbmFtZXMocmFuay5wcmVkaWN0KSA8LSBjb2xuYW1lcyhteWRpc3RtYXQpCnJvd25hbWVzKHJhbmsucHJlZGljdCkgPC0gYygxOjYwKQoKZm9yIChpIGluIDE6NjApewpteWRpc3RtYXQub3JkZXJlZCA8LSBteWRpc3RtYXRbb3JkZXIobXlkaXN0bWF0WyxpXSksXQpyYW5rLnByZWRpY3RbLGldIDwtIHJvd25hbWVzKG15ZGlzdG1hdC5vcmRlcmVkKSAKfQoKcmFuayA8LSAxOjU5CnJhbmsucHJlZGljdCA8LSByYW5rLnByZWRpY3RbLTEsXQpyYW5rLnByZWRpY3QgPC0gYXMuZGF0YS5mcmFtZShjYmluZChyYW5rLCByYW5rLnByZWRpY3QpKQoKCndyaXRlLmNzdjIocmFuay5wcmVkaWN0LCAicmFua3ByZWRpY3QuY3N2IikKcmFuay5wcmVkaWN0CmBgYAoKRE1TTyBwaWxvdCB0ZXN0IGZvciBmaWd1cmUgMQoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KI3dvcmtpbmcgZGlyZWN0b3J5CnNldHdkKCIuL0RNU09fdGVzdC8iKQoKI2NvbXBvdW5kcwpETVNPX3Rlc3QucmF3PC1yZWFkLmNzdjIoZmlsZT0iRE1TT190ZXN0X3Jhdy5jc3YiLCBoZWFkZXI9VCkKCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojTm9ybWFsaXphdGlvbgp4PC1hcy5tYXRyaXgoRE1TT190ZXN0LnJhd1ssMjo5N10pCm5vcm08LXhbMSxdCm5vcm08LWFzLnZlY3Rvcihub3JtKQpETVNPX3Rlc3Qubm9ybTwteC9yZXAobm9ybSwgZWFjaCA9IG5yb3coeCkpCkRNU09fdGVzdC5ub3JtPC1ETVNPX3Rlc3Qubm9ybVsyOjE2MyxdICNyZW1vdmUgZmlyc3Qgcm93IChsYXN0IG1lYXN1cmVtZW50IGJlZm9yZSBjb21wb3VuZCBhZGRpdGlvbikKCiAgICAKICAgICAgICAgIAojc2V0IG1lYXN1cmVtZW50CkRNU09fdGVzdC5ub3JtPC1ETVNPX3Rlc3Qubm9ybVsxOjE1MCxdCkRNU09fdGVzdC5ub3JtIDwtIGFzLmRhdGEuZnJhbWUoRE1TT190ZXN0Lm5vcm0pCgojYm94cGxvdApwb3N0c2NyaXB0KCJmaWd1cmVfMS5lcHMiLCB3aWR0aCA9IDg2MCwgaGVpZ2h0ID0gNjAwKQpwYXIobWFyPWMoNSwzLDIsMikrMC4xKQpib3hwbG90KERNU09fdGVzdC5ub3JtLCAgeWxhYiA9ICJOQ0kiLCB4bGFiID0gIndlbGwgcG9zaXRpb24iLCBjZXguYXhpcz0wLjQsIGxhcz0yLCBjb2wgPSAibGlnaHRncmF5IikKZGV2Lm9mZigpCgoKIyBwbG90IHRoZSBUQ1JQcwpteV90aW1lcG9pbnRzIDwtIERNU09fdGVzdC5yYXdbMjoxNTEsXSR0IC0gRE1TT190ZXN0LnJhd1syLF0kdApyb3duYW1lcyhETVNPX3Rlc3Qubm9ybSkgPC0gbXlfdGltZXBvaW50cwoKI3BkZihmaWxlID0gIkRNU09fY29udHJvbHMucGRmIiwgcGFwZXIgPSAiYTRyIikKcGFyKG1mcm93ID0gYygyLDMpKQpmb3IgKGkgaW4gMTogbmNvbChETVNPX3Rlc3Qubm9ybSkpewpwbG90KERNU09fdGVzdC5ub3JtWyxpXSwgdHlwZT0ibCIsIGNvbD0iYmx1ZSIsCiAgICAgbWFpbj1jb2xuYW1lcyhETVNPX3Rlc3Qubm9ybSlbaV0sY2V4Lm1haW49MC44LCB5bGltPWMoMC44LDIpLCB5bGFiPSJOQ0kiLCB4bGFiPSJ0IFtoXSIpCn0KI2Rldi5vZmYoKQoKI2NhbGN1bGF0ZSBtZWRpYW5zCm15X21hdHJpeCA8LSBhcy5tYXRyaXgoRE1TT190ZXN0Lm5vcm0pCmNvbF9tZWRpYW5zIDwtIGFwcGx5KG15X21hdHJpeCwgMiwgbWVkaWFuKQoKI3dpbGNveCB0ZXN0CmNvbF9tZWRpYW5zWyJHNyJdPC0gTkEKY29sX21lZGlhbnNbIkgyIl0gPC0gTkEKY29sX21lZGlhbnNbIkg1Il0gPC0gTkEKY29sX21lZGlhbnNbIkgxMCJdIDwtIE5BCgpyb3dfQSA8LSBjb2xfbWVkaWFuc1sxOjEyXQpyb3dfQiA8LSBjb2xfbWVkaWFuc1sxMzoyNF0Kcm93X0MgPC0gY29sX21lZGlhbnNbMjU6MzZdCnJvd19EIDwtIGNvbF9tZWRpYW5zWzM3OjQ4XQpyb3dfRSA8LSBjb2xfbWVkaWFuc1s0OTo2MF0Kcm93X0YgPC0gY29sX21lZGlhbnNbNjE6NzJdCnJvd19HIDwtIGNvbF9tZWRpYW5zWzczOjg0XQpyb3dfR1siRzYiXSA8LSBOQQpyb3dfR1siRzciXSA8LSBOQQpyb3dfSCA8LSBjb2xfbWVkaWFuc1s4NTo5Nl0Kcm93X0hbIkg1Il0gPC0gTkEKcm93X0hbIkgxMCJdIDwtIE5BCgpvdXRlcl9yb3dzIDwtIGMocm93X0EsIHJvd19IKQppbm5lcl9yb3dzIDwtIGMocm93X0IsIHJvd19DLCByb3dfRCwgcm93X0UsIHJvd19GLCByb3dfRykKbWVkaWFuKG91dGVyX3Jvd3MsIG5hLnJtID0gVCkKbWVkaWFuKGlubmVyX3Jvd3MsIG5hLnJtID0gVCkKCndpbGNveC50ZXN0KG91dGVyX3Jvd3MsIGlubmVyX3Jvd3MsIG5hLnJtID0gVCwgYWx0ZXJuYXRpdmUgPSAidHdvLnNpZGVkIikKCmBgYAoK